﻿using System;
using System.Collections.Generic;

namespace SanlieHash.Encoder
{
    public class Base64 : IEncoder
    {
        internal const byte _ext3 = (byte)'=';
        internal static byte[] Base64_MAP;
        internal static Dictionary<byte, byte> Base64_MAP_map = new Dictionary<byte, byte>(1<<6 +1);
        static Base64()
        {
            MakeMap(string.Empty);
        }
        public static void MakeMap(string textmap)
        {
            var map = new byte[1<<6];
            if (textmap.Length != map.Length){
                int i;
                for (i = 0; i < 26; i++) map[i] = (byte)('A' + i);
                for (i = 0; i < 26; i++) map[26+i] = (byte)('a' + i);
                for (i = 0; i < 10; i++) map[52+i] = (byte)('0' + i);
                map[62] = (byte)'+';
                map[63] = (byte)'/';
            } else for (var i = 0; i < map.Length; i++) map[i] = (byte)textmap[i];

            for (var i = 0; i < map.Length; i++) Base64_MAP_map[map[i]] = (byte)i;
            Base64_MAP_map[_ext3] = 0;
            Base64_MAP = map;
        }

        internal const int MinBytes = 3;
        internal const int OutBytes = 4;
        internal uint BufLEN { get;set; }
        public byte[] Buffer { get; internal set; }
        public int DecodeMaxLength => (int)(BufLEN / MinBytes * OutBytes);
        public int EncodeMaxLength => (int)(BufLEN / OutBytes * MinBytes);
        public Base64(uint buflen = 128)
        {
            BufLEN = buflen + (OutBytes* MinBytes - buflen% (OutBytes* MinBytes));
            Buffer = new byte[BufLEN];
        }

        public int Decode(byte[] byteInput, int start, int length)
        {
            if (length + start > byteInput.Length) length = byteInput.Length - start;
            if (length == 0) return 0;
            //超出缓存大小
            if (length > DecodeMaxLength) throw new ArgumentOutOfRangeException();
            int i, j;
            byte a, b, c, d;
            length += start;
            for (i = start, j = 0; i < length; i+= OutBytes, j+= MinBytes)
            {
                a = Base64_MAP_map[byteInput[i]];
                b = Base64_MAP_map[byteInput[i+1]];
                c = Base64_MAP_map[byteInput[i+2]];
                d = Base64_MAP_map[byteInput[i+3]];

                Buffer[j] = (byte)(a << 2 | b >> 4);
                Buffer[j+1] = (byte)(b << 4 | c >> 2);
                Buffer[j+2] = (byte)(c << 6 | d);
            }
            if (byteInput[i - 1] == _ext3) j--;
            if (byteInput[i - 2] == _ext3) j--;
            return j;
        }

        public int Encode(byte[] byteInput, int start, int length)
        {
            if (length + start > byteInput.Length) length = byteInput.Length - start;
            if (length == 0 ) return 0;
            //超出缓存大小
            if (length > EncodeMaxLength) throw new ArgumentOutOfRangeException();
            int i, j;

            int len = length - length % MinBytes + start;
            length += start;
            for (i = start, j = 0; i < len; i += MinBytes, j += OutBytes)
            {
                Buffer[j] = Base64_MAP[(byteInput[i] >> 2) & 0b111111];
                Buffer[j + 1] = Base64_MAP[((byteInput[i] << 4) & 0b110000) | ((byteInput[i + 1] >> 4) & 0b1111)];
                Buffer[j + 2] = Base64_MAP[((byteInput[i +1] << 2) & 0b111100) | ((byteInput[i +2] >> 6) & 0b11)];
                Buffer[j + 3] = Base64_MAP[byteInput[i +2] & 0b111111];
            }

            if (len < length)
            {
                if (len +2 > length)
                {
                    Buffer[j++] = Base64_MAP[(byteInput[len] >> 2) & 0b111111];
                    Buffer[j++] = Base64_MAP[((byteInput[i] << 4) & 0b110000)];
                    Buffer[j++] = _ext3;
                    Buffer[j++] = _ext3;
                }
                else
                {
                    Buffer[j++] = Base64_MAP[(byteInput[len] >> 2) & 0b111111];
                    Buffer[j++] = Base64_MAP[((byteInput[i] << 4) & 0b110000) | ((byteInput[i + 1] >> 4) & 0b1111)];
                    Buffer[j++] = Base64_MAP[(byteInput[i + 1] << 2) & 0b111100];
                    Buffer[j++] = _ext3;
                }
            }
            return j;
        }
    }
}
